package com.yicheng.statistics.common

import java.awt.Polygon

import scala.util.{Failure, Success, Try}


/**
  * Created by yuer on 16/10/7.
  */


case class GisPoint(x: Double, y: Double)extends Ordered[GisPoint]{
  override def compare(that: GisPoint): Int = {
    val first = x.compare(that.x)
    if(first!=0)
      first
    else
      y.compare(that.y)
  }
}

object GisMapUtil {

  val DEF_PI = 3.14159265359
  // PI

  val DEF_2PI = 6.28318530712
  // 2*PI

  val DEF_PI180 = 0.01745329252
  // PI/180.0

  val DEF_R = 6370693.5
  // radius of earth

  val EARTH_RADIUS = 6378137.0

  val x_pi: Double = 3.14159265358979324 * 3000.0 / 180.0

  //地球半径
  def rad(d: Double): Double = {
    d * Math.PI / 180.0
  }

  def GetShortDistance(lng1: Double, lat1: Double, lng2: Double, lat2: Double): Double = {
    val radLat1 = rad(lat1)
    val radLat2 = rad(lat2)
    val a = radLat1 - radLat2
    val b = rad(lng1) - rad(lng2)
    val s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
      Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))) * EARTH_RADIUS

    Math.round(s * 10000) / 10000.0
  }

  def GetLongDistance(lon1: Double, lat1: Double, lon2: Double, lat2: Double): Double = {

    val (ew1, ns1, ew2, ns2) = (lon1 * DEF_PI180, lat1 * DEF_PI180, lon2 * DEF_PI180, lat2 * DEF_PI180)

    var distance = Math.sin(ns1) * Math.sin(ns2) + Math.cos(ns1) * Math.cos(ns2) * Math.cos(ew1 - ew2)

    if (distance > 1.0) {
      distance = 1.0
    } else if (distance < -1.0) {
      distance = -1.0
    }
    DEF_R * Math.acos(distance)
  }

  def outOfChina(lon: Double, lat: Double): Boolean = {
    (lon < 72.004 || lon > 137.8347) && (lat < 0.8293 || lat > 55.8271)
  }

  /**
    * 判断点是否在园内
    **/
  def IsPointInCircle(point: GisPoint, gisPoints: List[GisPoint], radius: Float): Boolean = {
    if (gisPoints.isEmpty) {
      false
    } else {
      val circlePoint = gisPoints.head
      val realRadius = GetShortDistance(point.x, point.y, circlePoint.x, circlePoint.y)
      realRadius <= radius
    }
  }

  /**
    * 判断点是否在多边形区域内
    **/
  @Deprecated
  def IsPointInRegion(point: GisPoint, gisPoints: List[GisPoint]): Boolean = {
    val TIMES = 1000000
    if (gisPoints.lengthCompare(3) < 0) {
      false
    } else {
      val polygon = new Polygon
      gisPoints.foreach { gispoint =>
        polygon.addPoint((gispoint.x * TIMES).toInt, (gispoint.y * TIMES).toInt)
      }
      val px = (point.x * TIMES).toInt
      val py = (point.y * TIMES).toInt
      polygon.contains(px, py)
    }
  }

  /**
    * 判断点是否在矩形区域内
    **/
  def isPointInRegion(point: GisPoint, gisPoints: List[GisPoint]): Boolean = {
    if (gisPoints.lengthCompare(3) > 0) {
      val p1 = gisPoints.min
      val p2 = gisPoints.max
      (p1.x <= point.x && point.x <= p2.x) && (p1.y <= point.y && point.y <= p2.y)
    }
    else {
      false
    }
  }
  /**
    * 判断点是否在多边形区域内
    **/
  def isPointInPolygon(point: GisPoint, gisPoints: List[GisPoint]): Boolean = {
    if (gisPoints.lengthCompare(3) > 0) {
      val index = gisPoints.indices.toList
      val indexed = index.takeRight(1) ++ index.dropRight(1)
      val pps = index.zip(indexed).map { xp =>
        (gisPoints(xp._1), gisPoints(xp._2))
      }
      pps.filter {
        pp =>
          (pp._1.y < point.y && pp._2.y >= point.y) ||
            (pp._2.y < point.y && pp._1.y >= point.y)
      }.map {
        pp1 =>
          val sum = pp1._1.x + ((point.y - pp1._1.y) / (pp1._2.y - pp1._1.y)) * (pp1._2.x - pp1._1.x)
          if (sum < point.x)
            1
          else
            0
      }.sum % 2 match {
        case 0 =>
          false
        case 1 =>
          true
      }
    } else {
      false
    }
  }

  def transformLat(x: Double, y: Double): Double = {
    var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x))
    ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0
    ret += (20.0 * Math.sin(y * Math.PI) + 40.0 * Math.sin(y / 3.0 * Math.PI)) * 2.0 / 3.0
    ret += (160.0 * Math.sin(y / 12.0 * Math.PI) + 320 * Math.sin(y * Math.PI / 30.0)) * 2.0 / 3.0
    ret
  }

  def transformLon(x: Double, y: Double): Double = {
    var ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x))
    ret += (20.0 * Math.sin(6.0 * x * Math.PI) + 20.0 * Math.sin(2.0 * x * Math.PI)) * 2.0 / 3.0
    ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * Math.sin(x / 3.0 * Math.PI)) * 2.0 / 3.0
    ret += (150.0 * Math.sin(x / 12.0 * Math.PI) + 300.0 * Math.sin(x / 30.0 * Math.PI)) * 2.0 / 3.0
    ret
  }

  def delta(lon: Double, lat: Double): (Double, Double) = {
    val a = 6378245.0
    //  a: 卫星椭球坐标投影到平面地图坐标系的投影因子。
    val ee = 0.00669342162296594323
    //  ee: 椭球的偏心率。
    var dLat = transformLat(lon - 105.0, lat - 35.0)
    var dLon = transformLon(lon - 105.0, lat - 35.0)
    val radLat = lat / 180.0 * Math.PI
    var magic = Math.sin(radLat)
    magic = 1 - ee * magic * magic
    val sqrtMagic = Math.sqrt(magic)
    dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI)
    dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * Math.PI)
    (dLon, dLat)
  }

  def bd_encrypt(gcjLon: Double, gcjLat: Double): (Double, Double) = {
    val x = gcjLon
    val y = gcjLat
    val z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * x_pi)
    val theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * x_pi)
    val bdLon = z * Math.cos(theta) + 0.0065
    val bdLat = z * Math.sin(theta) + 0.006
    val lon: Double = Math.round(bdLon * 1000000) / 1000000.0
    val lat: Double = Math.round(bdLat * 1000000) / 1000000.0
    (lon, lat)
  }

  def gcj_encrypt(wgsLon: Double, wgsLat: Double): (Double, Double) = {
    if (outOfChina(wgsLon, wgsLat)) {
      (wgsLon, wgsLat)
    } else {
      val d = delta(wgsLon, wgsLat)
      (wgsLon + d._1, wgsLat + d._2)
    }

  }

  def convert2baidu(lon: Double, lat: Double): (Double, Double) = {
    Try {
      bd_encrypt(gcj_encrypt(lon, lat)._1, gcj_encrypt(lon, lat)._2)
    } match {
      case Success(result) => result
      case Failure(_) => (lon, lat)
    }
  }
}
